package com.uduemc.biso.node.web.api.filter;

import cn.hutool.core.util.StrUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.uduemc.biso.core.extities.center.Host;
import com.uduemc.biso.core.extities.center.HostSetup;
import com.uduemc.biso.core.extities.center.Site;
import com.uduemc.biso.core.extities.node.custom.LoginNode;
import com.uduemc.biso.core.utils.JsonResult;
import com.uduemc.biso.core.utils.RedisUtil;
import com.uduemc.biso.node.core.property.GlobalProperties;
import com.uduemc.biso.node.web.api.component.Identification;
import com.uduemc.biso.node.web.api.component.RequestHolder;
import com.uduemc.biso.node.web.api.component.SpringContextUtils;
import com.uduemc.biso.node.web.api.service.DomainService;
import com.uduemc.biso.node.web.api.service.HostService;
import com.uduemc.biso.node.web.utils.IpUtil;
import com.uduemc.biso.node.web.utils.JwtTokenUtils;
import com.uduemc.biso.node.web.utils.ResubmitUtil;
import io.jsonwebtoken.JwtException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.MediaType;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.CollectionUtils;
import org.springframework.util.PathMatcher;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.annotation.Resource;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;

public class AuthenticationFilter extends OncePerRequestFilter {

    private static final Logger logger = LoggerFactory.getLogger(AuthenticationFilter.class);

    @Resource
    private RedisUtil redisUtil;

    @Resource
    private RequestHolder requestHolder;

    @Resource
    private ObjectMapper objectMapper;

    @Resource
    private GlobalProperties globalProperties;

    @Resource
    private DomainService domainServiceImpl;

    @Resource
    private Identification identification;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {

        logger.info("AuthenticationFilter");
        logger.info("request uri :" + request.getRequestURI());
        logger.info("request url :" + request.getRequestURL());
        logger.info("servlet path : " + request.getServletPath());

        JsonResult resubmit = resubmit(request);
        if (resubmit != null) {
            // 获取到了防止重复提交的返回数据，这里直接返回。
            response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
            response.getWriter().print(objectMapper.writeValueAsString(resubmit));
            return;
        }

        /**
         * 这里要过滤是否是登录或者登出接口，如果是则直接放行
         */
        List<String> noAuthorization = globalProperties.getNodeUrl().getNoAuthorization();
        if (CollectionUtils.isEmpty(noAuthorization)) {
            filterChain.doFilter(request, response);
            return;
        }
        PathMatcher pathMatcher = new AntPathMatcher();
        for (String url : noAuthorization) {
            if (pathMatcher.match(url, request.getRequestURI())) {
                filterChain.doFilter(request, response);
                return;
            }
        }

        // 验证并获取 token
        String token = request.getHeader("Authorization");
        if (!StringUtils.hasText(token)) {
            token = request.getParameter("jsessionid");
            if (!StringUtils.hasText(token)) {
                logger.error("未能获取 jsessionid ! request.getRequestURI(): " + request.getRequestURI());
                // 未能获取到用户数据
                response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
                response.getWriter().print(objectMapper.writeValueAsString(JsonResult.noLogin()));
                return;
            }
        }

        String sessionKey = null;
        try {
            sessionKey = JwtTokenUtils.getUsername(token);
        } catch (JwtException eje) {
            logger.info(eje.getMessage());
            // 未能获取到用户数据
            response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
            response.getWriter().print(objectMapper.writeValueAsString(JsonResult.noLogin()));
            return;
        }

        if (!StringUtils.hasText(sessionKey)) {
            logger.info("未能获取 sessionKey !");
            // 未能获取到用户数据
            response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
            response.getWriter().print(objectMapper.writeValueAsString(JsonResult.noLogin()));
            return;
        }

        String redisKey = globalProperties.getNodeRedisKey().makeNodeRedisKey(sessionKey);
        if (!StringUtils.hasText(redisKey)) {
            logger.info("未能获取 redisKey !");
            // 未能获取到用户数据
            response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
            response.getWriter().print(objectMapper.writeValueAsString(JsonResult.noLogin()));
            return;
        }
        LoginNode loginNode = (LoginNode) redisUtil.get(redisKey);
        if (loginNode == null || loginNode.getLoginUserId().longValue() < 1L) {
            // 未能获取到用户数据
            response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
            response.getWriter().print(objectMapper.writeValueAsString(JsonResult.noLogin("身份验证失败！")));
            return;
        }

        // 验证 loginNode 中的 ip、agent 是否与现在的对应的上
        if (!identificationLoginNode(loginNode, request)) {
            String ip = loginNode.getIp();
            String agent = loginNode.getAgent();
            logger.info("loginNode ip: " + ip);
            logger.info("loginNode agent: " + agent);
            logger.info("ip: " + IpUtil.getIpAddr(request));
            logger.info("agent: " + request.getHeader("user-agent"));
            // 获取到用户数据未能验证通过
            response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
            response.getWriter().print(objectMapper.writeValueAsString(JsonResult.noLogin("身份验证失败！")));
            return;
        }

        // 获取当前操作的 site_id
        Long currentSiteId = loginNode.getCurrentSiteId();
        // 如果为空
        if (currentSiteId == null || currentSiteId.longValue() < 1L) {
            // 未能获取到用户数据
            logger.error("当前的site不可用！");
            response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
            response.getWriter().print(objectMapper.writeValueAsString(JsonResult.noLogin()));
            return;
        }
        List<Site> sites = loginNode.getSites();
        // 只有当前的site状态为1方可放行
        for (Site site : sites) {
            if (site.getId().longValue() == currentSiteId) {
                if (site.getStatus().shortValue() != (short) 1) {
                    logger.error("当前的site无法正常操作！");
                    response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
                    response.getWriter().print(objectMapper.writeValueAsString(JsonResult.noLogin()));
                    return;
                }
                requestHolder.add(site);
                break;
            }
        }

        Site currentSite = requestHolder.getCurrentSite();
        if (currentSite == null || currentSite.getId().longValue() < 1L) {
            logger.error("无法获取当前操作的 site");
            response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
            response.getWriter().print(objectMapper.writeValueAsString(JsonResult.noLogin()));
            return;
        }

        // 通过标识识别重新登录
        if (identificationRelogin(loginNode)) {
            // 需要重新登录
            // 未能获取到用户数据
            response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
            response.getWriter().print(objectMapper.writeValueAsString(JsonResult.noLogin("站点数据更新，需要重新登录！")));
            return;
        }

        // 通过标识识别更新动作
        boolean bool = identificationHotUpdate(loginNode);

        /**
         * 将 loginNode 写入 ThreadLocal 中
         */
        requestHolder.add(token);
        requestHolder.add(request);
        requestHolder.add(loginNode);
        requestHolder.add(domainServiceImpl.getDefaultDomain());

        LoginNode temp = requestHolder.getCurrentLoginNode();
        if (temp == null || temp.getLoginUserId().longValue() < 1L) {
            response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
            response.getWriter().print(objectMapper.writeValueAsString(JsonResult.noLogin()));
            return;
        }
        request.setAttribute("loginNode", loginNode);

        filterChain.doFilter(request, response);

        requestHolder.remove();

        if ((redisUtil.get(redisKey) != null && redisUtil.getExpire(redisKey) < 60 * 40) || bool) {
            // 重新设置缓存
            if (redisUtil.set(redisKey, loginNode, 3600L * 4) == false) {
                redisUtil.del(redisKey);
                logger.error("重新设置缓存异常！");
            }
        }
    }

    protected boolean identificationLoginNode(LoginNode loginNode, HttpServletRequest request) {
        // 验证 loginNode 中的 ip、agent 是否与现在的对应的上
        String ip = loginNode.getIp();
        if (StrUtil.isBlank(ip)) {
            return false;
        }
        if (!ip.equals("*") && !ip.equals(IpUtil.getIpAddr(request))) {
            return false;
        }

        String agent = loginNode.getAgent();
        if (StrUtil.isBlank(agent)) {
            return false;
        }
        if (!agent.equals("*") && !agent.equals(request.getHeader("user-agent"))) {
            return false;
        }
        return true;
    }

    protected JsonResult resubmit(HttpServletRequest request) {
        String redisKey = ResubmitUtil.redisKey(request);
        Object result = redisUtil.get(redisKey);
        if (result == null) {
            return null;
        }
        return (JsonResult) result;

    }

    protected boolean identificationRelogin(LoginNode loginNode) {
        String redisKey = loginNode.getRedisKey();
        return identification.relogin(loginNode.getHost().getId(), redisKey);
    }

    /**
     * @param loginNode
     */
    public boolean identificationHotUpdate(LoginNode loginNode) {
        String redisKey = loginNode.getRedisKey();
        boolean aHost = identification.aHost(loginNode.getHost().getId(), redisKey);
        if (aHost) {
            // 重新从数据库中虎丘 host 数据，并且替换到 loginNode 中的 host 数据
            HostService hostServiceImpl = SpringContextUtils.getBean("hostServiceImpl", HostService.class);
            try {
                Host host = hostServiceImpl.getInfoById(loginNode.getHost().getId());
                loginNode.setHost(host);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        HostSetup aHostSetup = identification.aHostSetup(loginNode.getHost().getId(), redisKey);
        if (aHostSetup != null) {
            // 重新从数据库中虎丘 host 数据，并且替换到 loginNode 中的 host 数据
            loginNode.setHostSetup(aHostSetup);
        }
        // 或的关系
        return aHost || aHostSetup != null;
    }

}
